
import { MapElement, MapElementOption } from './MapElement'
import { MouseHandler } from './MouseHandler'
import Stats from 'three/examples/jsm/libs/stats.module'
import { Ground } from './Ground'

import { Scene, Vector2, Vector3, WebGLRenderer } from 'three'

import { Stage, Stage2D, Stage3D } from './Stage'
import { PROJECTION } from '../Util/static'

export type MapMode = '2D' | '3D'
export class CqkjMap {
  private _anim = 0

  readonly dom: HTMLDivElement
  readonly renderer: WebGLRenderer
  readonly scene: Scene

  readonly mouseHandler: MouseHandler

  readonly elements = new Map<string, MapElement<MapElementOption>>()

  readonly ground: Ground

  public stage: Stage

  get mode() {
    return this.stage.mode
  }

  set mode(val) {
    const { center, zoom } = this
    this.stage.destroy()
    this.stage = val == '3D' ? new Stage3D(this) : new Stage2D(this)
    this.setView(center[0], center[1], zoom)
    this.elements.forEach(e => e.show && e.onModeChange?.())
  }

  get camera() {
    return this.stage.camera
  }

  get control() {
    return this.stage.control
  }

  get show(): boolean {
    return this.dom.parentElement instanceof HTMLElement
  }

  get size() {
    return this.renderer.getSize(new Vector2())
  }

  get center() {
    return [PROJECTION.xToLon(this.stage.control.target.x), PROJECTION.yToLat(this.stage.control.target.y)]
  }

  get zoom() {
    const distance = this.control.getDistance()
    return Math.log2(156543.0339280412 * this.dom.offsetHeight / distance)
  }

  constructor() {
    this._render = this._render.bind(this)
    this.dom = createDom()
    this.renderer = createRenderer(this.dom)
    this.scene = new Scene()
    this.ground = new Ground(this)

    this.scene.add(this.ground.mesh)
    this.stage = new Stage3D(this)
    this.mouseHandler = new MouseHandler(this)
  }



  addTo(div: HTMLDivElement | string): this {
    const container = div instanceof HTMLDivElement ? div : document.getElementById(div)
    if (container instanceof HTMLDivElement) {
      const { dom } = this
      if (dom.parentElement != null) {
        dom.remove()
      }
      container.appendChild(dom)
      this._render()
    } else {
      throw new Error('div 不存在')
    }
    return this
  }

  remove() {
    this.dom.remove()
    cancelAnimationFrame(this._anim)
    this._anim = 0
  }

  private _render() {
    this.stage.render()
    this._anim = requestAnimationFrame(this._render)
  }

  /**
   * 世界坐标转画布坐标
   * @param p 世界坐标
   * @returns 画布坐标
   */
  worldToPanel(p: Vector3) {
    p.project(this.camera)
    const w = this.dom.offsetWidth / 2
    const h = this.dom.offsetHeight / 2
    p.x = p.x * w + w
    p.y = -p.y * h + h
    return p
  }

  /**
   * 画布坐标转世界坐标
   * @param p 画布坐标
   * @returns 世界坐标
   */
  panelToWorld(p: Vector3) {
    const w = this.dom.offsetWidth / 2
    const h = this.dom.offsetHeight / 2
    p.x = p.x / w - 1
    p.y = 1 - p.y / h
    return p.unproject(this.camera)
  }



  setView(lon: number, lat: number, zoom: number = this.zoom) {
    const x = PROJECTION.lonToX(lon)
    const y = PROJECTION.latToY(lat)
    const l = 156543.0339280412 * this.dom.offsetHeight / Math.pow(2, zoom)
    switch (this.mode) {
      case '2D':
        this.stage.setView([x, y, 0.8660254037844386 * l, x, y, 0])
        break;
      case '3D':
        this.stage.setView([x, y - l / 2, 0.8660254037844386 * l, x, y, 0])
        break;
    }

  }

  /** 查看帧率 */
  showRenderStatus() {
    const stats = Stats()
    const _render = this._render
    this._render = () => {
      _render()
      stats.update()
    }
    this.dom.appendChild(stats.domElement)
    // eslint-disable-next-line @typescript-eslint/no-empty-function
    this.showRenderStatus = () => { }
  }

  /** 放大 */
  zoomIn() {
    const { camera, control: { target }, stage } = this
    const position = camera.position.clone().add(target).divideScalar(2)
    stage.setView([position.x, position.y, position.z, target.x, target.y, target.z], 300)
  }

  /** 缩小 */
  zoomOut() {
    const { camera, control: { target }, stage } = this
    const position = camera.position.clone().sub(target).multiplyScalar(2).add(target)
    stage.setView([position.x, position.y, position.z, target.x, target.y, target.z], 300)
  }

  /**
   * 保持观察角度飞往观察目标指定距离处
   * @param _target 观察目标的坐标
   * @param distance 观察距离
   */
  zoomTo(_target: Vector3, distance?: number) {
    const { camera, control, stage } = this
    const l = distance ?? camera.position.distanceTo(control.target)
    const position = camera.position.clone().sub(control.target).normalize().multiplyScalar(l).add(_target)
    const target = _target.clone().sub(position).multiplyScalar(position.z / (position.z - _target.z)).add(position)
    stage.setView([position.x, position.y, position.z, target.x, target.y, target.z], 300)
  }
}



function createDom() {
  const div = document.createElement('div')
  div.style.position = 'absolute'
  div.style.left = '0'
  div.style.top = '0'
  div.style.width = '100%'
  div.style.height = '100%'
  div.style.overflow = 'hidden'
  return div
}


function createRenderer(dom: HTMLDivElement) {
  const renderer = new WebGLRenderer()
  dom.appendChild(renderer.domElement)
  renderer.setPixelRatio(window.devicePixelRatio)
  renderer.setClearColor(0x101912)
  renderer.autoClear = false
  return renderer
}